
package com.kugou.game.sdk.hotfix;

import com.kugou.game.sdk.persistence.preference.PrefUtil;
import com.kugou.game.sdk.statistics.StaticsManager;

import dalvik.system.DexFile;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Build.VERSION;
import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.zip.ZipFile;

/**
 * 描述:加载dex类
 *
 * @author jerryliu
 * @since 2016年3月15日 下午2:20:36
 */
public final class MultiDex {
    private static final String TAG = "MultiDex";

    private static final String SECONDARY_FOLDER_NAME = "kgdex";

    public static final String PATCH_FOLDER_NAME = "kgpatch";

    // 已经加载的版本号
    public static final String USEPATCHCODE = "usepatchcode";

    private static MultiDexType currentType;

    private MultiDex() {
    }

    public enum MultiDexType {

        PATCH("patch"), HACK("kg_hack_dex");

        private String name;

        private MultiDexType(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    public static boolean install(Context context, MultiDexType type) {
        currentType = type;
        Log.d(TAG, "install " + type.name);
        try {
            ApplicationInfo e = context.getApplicationInfo();
            if (e == null) {
                return false;
            }
            synchronized (MultiDex.class) {
                ClassLoader loader;
                try {
                    loader = context.getClassLoader();
                } catch (RuntimeException var9) {
                    Log.w(TAG, "Failure while trying to obtain Context class loader. ", var9);
                    return false;
                }

                if (loader == null) {
                    Log.w(TAG, "Context class loader is null. ");
                    return false;
                }

                File dexDir = new File(e.dataDir, SECONDARY_FOLDER_NAME);
                List<File> files = MultiDexExtractor.load(context, e, dexDir, type);
                if (files.isEmpty()) {
                    return false;
                }
                installSecondaryDexes(loader, dexDir, files);
            }
        } catch (Exception var11) {
            Log.w(TAG, "Multidex installation failure", var11);
            throw new RuntimeException("Multidex installation failed (" + var11.getMessage() + ").");
        }
        Log.d(TAG, "install done " + type.name);
        return true;
    }

    public static boolean loadPatch(Context context, int sdkVersionCode) {
        currentType = MultiDexType.PATCH;
        Log.d(TAG, "start load patch...");
        try {
            ApplicationInfo e = context.getApplicationInfo();
            if (e == null) {
                return false;
            }
            synchronized (MultiDex.class) {
                ClassLoader loader;
                try {
                    loader = context.getClassLoader();
                } catch (RuntimeException var9) {
                    Log.w(TAG, "Failure while trying to obtain Context class loader. ", var9);
                    return false;
                }

                if (loader == null) {
                    Log.w(TAG, "Context class loader is null.");
                    return false;
                }

                File dexDir = new File(e.dataDir, PATCH_FOLDER_NAME);
                dexDir.mkdir();
                // AssetUtils.copyAsset(context, "patch_550_16031301.jar",
                // dexDir);

                List<File> files = MultiDexExtractor.loadPatch(context, e, dexDir, sdkVersionCode);
                if (files.isEmpty()) {
                    return false;
                }
                installSecondaryDexes(loader, dexDir, files);
            }
        } catch (Exception var11) {
            // 发送统计--加载补丁失败
            final String errorlog = getLoadPatchFaileReport(var11);
            int patchid = PrefUtil.getUsePatchId(context, MultiDex.USEPATCHCODE);
            StaticsManager.sendPatchLogStatics(context, 2, patchid, errorlog);

            Log.w(TAG, "Multidex installation failure", var11);
            throw new RuntimeException("Multidex installation failed (" + var11.getMessage() + ").");
        }

        int patchid = PrefUtil.getUsePatchId(context, MultiDex.USEPATCHCODE);
        Log.d(TAG, "install patch done :" + patchid);
        // 发送统计--加载补丁成功
        StaticsManager.sendPatchLogStatics(context, 1, patchid, "");
        return true;
    }

    public static String getLoadPatchFaileReport(Throwable ex) {
        StringBuffer sb = new StringBuffer("");
        try {
            if (ex != null) {
                StringWriter writer = new StringWriter();
                PrintWriter printWriter = new PrintWriter(writer);
                ex.printStackTrace(printWriter);
                Throwable cause = ex.getCause();
                while (cause != null) {
                    cause.printStackTrace(printWriter);
                    cause = cause.getCause();
                }
                printWriter.close();
                String result = writer.toString();
                sb.append(result);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sb.toString() + "\nPID=" + android.os.Process.myPid();
    }

    private static void installSecondaryDexes(ClassLoader loader, File dexDir, List<File> files)
            throws Exception {

        if (!files.isEmpty()) {
            try {
                if (VERSION.SDK_INT >= 23) {
                    V23.install(loader, files, dexDir);
                } else if (VERSION.SDK_INT >= 19) {
                    V19.install(loader, files, dexDir);
                } else if (VERSION.SDK_INT >= 14) {
                    V14.install(loader, files, dexDir);
                } else {
                    V4.install(loader, files);
                }
            } catch (Exception e) {
                // 此处抛出异常，可能由于低版本SDK使用了高版本SDK的PathClassLoader,反之亦有可能，尝试重新加载
                if (VERSION.SDK_INT >= 23) {
                    throw new Exception(e);
                } else if (VERSION.SDK_INT >= 19) {
                    throw new Exception(e);
                } else if (VERSION.SDK_INT >= 14) {
                    try {
                        V4.install(loader, files);
                    } catch (Exception e1) {
                        throw new Exception(e);
                    }
                } else {
                    try {
                        V14.install(loader, files, dexDir);
                    } catch (Exception e1) {
                        throw new Exception(e);
                    }
                }
            }
        }

    }

    private static Field findField(Object instance, String name) throws NoSuchFieldException {
        Class clazz = instance.getClass();
        while (clazz != null) {
            try {
                Field e = clazz.getDeclaredField(name);
                if (!e.isAccessible()) {
                    e.setAccessible(true);
                }

                return e;
            } catch (NoSuchFieldException var4) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchFieldException("Field " + name + " not found in " + instance.getClass());
    }

    private static Method findMethod(Object instance, String name, Class... parameterTypes)
            throws NoSuchMethodException {
        Class clazz = instance.getClass();
        while (clazz != null) {
            try {
                Method e = clazz.getDeclaredMethod(name, parameterTypes);
                if (!e.isAccessible()) {
                    e.setAccessible(true);
                }
                return e;
            } catch (NoSuchMethodException var5) {
                clazz = clazz.getSuperclass();
            }
        }
        throw new NoSuchMethodException("Method " + name + " with parameters "
                + Arrays.asList(parameterTypes) + " not found in " + instance.getClass());
    }

    private static void expandFieldArray(Object instance, String fieldName, Object[] extraElements)
            throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException {
        Field jlrField = findField(instance, fieldName);
        Object[] original = ((Object[]) jlrField.get(instance));
        Object[] combined = ((Object[]) Array.newInstance(original.getClass().getComponentType(),
                original.length + extraElements.length));
        if (currentType == MultiDexType.HACK || currentType == MultiDexType.PATCH) {
            // 热修复时，要将对象放在数组的最前面
            System.arraycopy(extraElements, 0, combined, 0, extraElements.length);
            System.arraycopy(original, 0, combined, extraElements.length, original.length);
        } else {
            System.arraycopy(original, 0, combined, 0, original.length);
            System.arraycopy(extraElements, 0, combined, original.length, extraElements.length);
        }

        jlrField.set(instance, combined);
    }

    private static final class V4 {
        private V4() {
        }

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries)
                throws IllegalArgumentException, IllegalAccessException, NoSuchFieldException,
                IOException, Exception {
            int extraSize = additionalClassPathEntries.size();
            Field pathField = MultiDex.findField(loader, "path");
            StringBuilder path = new StringBuilder((String) pathField.get(loader));
            String[] extraPaths = new String[extraSize];
            File[] extraFiles = new File[extraSize];
            ZipFile[] extraZips = new ZipFile[extraSize];
            DexFile[] extraDexs = new DexFile[extraSize];

            String entryPath;
            int index;
            for (ListIterator iterator = additionalClassPathEntries.listIterator(); iterator
                    .hasNext(); extraDexs[index] = DexFile
                    .loadDex(entryPath, entryPath + ".dex", 0)) {
                File additionalEntry = (File) iterator.next();
                entryPath = additionalEntry.getAbsolutePath();
                path.append(':').append(entryPath);
                index = iterator.previousIndex();
                extraPaths[index] = entryPath;
                extraFiles[index] = additionalEntry;
                extraZips[index] = new ZipFile(additionalEntry);
            }

            pathField.set(loader, path.toString());
            MultiDex.expandFieldArray(loader, "mPaths", extraPaths);
            MultiDex.expandFieldArray(loader, "mFiles", extraFiles);
            MultiDex.expandFieldArray(loader, "mZips", extraZips);
            try {
                MultiDex.expandFieldArray(loader, "mDexs", extraDexs);
            } catch (Exception e) {
                e.printStackTrace();

                // 失败后尝试适配阿里云
                Object[] mLexs = new Object[extraDexs.length];
                for (int i = 0; i < extraDexs.length; i++) {
                    // 将DexFile值赋值给LexFile
                    Object lexFile = null;
                    try {
                        Class class1 = Class.forName("dalvik.system.LexFile");
                        Class[] argtype = new Class[] {
                                String.class, int.class
                        };
                        // public Lex(File file)
                        // public Lex(String mFileName)
                        // public Lex(String mFileName,int type)
                        // TYPE_DIR 2 |TYPE_LEX 1 | TYPE_UNKNOWN 0 | TYPE_ZIP 3
                        Object mFileName = getFieldValue(extraDexs[i], "mFileName");
                        // LexFile没有无参构造，这里获取了一个传String的构造，这里必须直接给定mFileName的值，LexFile代码里做了判断，路径不对直接捕获异常，没有抛出
                        // type默认是3，部分阿里云必须在构造函数里直接指明TYPE,这里传1，否则初始化失败
                        Object[] argparam = new Object[] {
                                mFileName, 1
                        };
                        Constructor constructor = class1.getDeclaredConstructor(argtype);
                        constructor.setAccessible(true);
                        lexFile = constructor.newInstance(argparam);
                        Log.d("DexClassLoader", "获取lexFile 实例成功");

                        Object mCookie = getFieldValue(extraDexs[i], "mCookie");
                        setFieldValue(lexFile, "mCookie", mCookie);
                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                    if (lexFile != null) {
                        mLexs[i] = lexFile;
                    }
                }

                MultiDex.expandFieldArray(loader, "mLexs", mLexs);

            }

        }
    }

    private static final class V14 {
        private V14() {
        }

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            Field pathListField = MultiDex.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            MultiDex.expandFieldArray(
                    dexPathList,
                    "dexElements",
                    makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries),
                            optimizedDirectory));
        }

        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files,
                File optimizedDirectory) throws IllegalAccessException, InvocationTargetException,
                NoSuchMethodException {
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements",
                    new Class[] {
                            ArrayList.class, File.class
                    });
            return ((Object[]) makeDexElements.invoke(dexPathList, new Object[] {
                    files, optimizedDirectory
            }));
        }
    }

    private static class V19 {
        private V19() {
        }

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            Field pathListField = MultiDex.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList suppressedExceptions = new ArrayList();
            try {
                MultiDex.expandFieldArray(
                        dexPathList,
                        "dexElements",
                        makeDexElements(dexPathList, new ArrayList(additionalClassPathEntries),
                                optimizedDirectory, suppressedExceptions));
            } catch (Exception e) {
                e.printStackTrace();
                MultiDex.expandFieldArray(
                        dexPathList,
                        "dexElements",
                        makeDexElementsByClassLoader(dexPathList, new ArrayList(
                                additionalClassPathEntries), optimizedDirectory,
                                suppressedExceptions, loader));
            }

            checkSuppressedExceptions(loader, suppressedExceptions);
        }

        protected static void checkSuppressedExceptions(ClassLoader loader,
                ArrayList suppressedExceptions) throws NoSuchFieldException, IllegalAccessException {
            if (suppressedExceptions.size() > 0) {
                Iterator suppressedExceptionsField = suppressedExceptions.iterator();

                while (suppressedExceptionsField.hasNext()) {
                    IOException dexElementsSuppressedExceptions = (IOException) suppressedExceptionsField
                            .next();
                    Log.w(TAG, "Exception in makeDexElement", dexElementsSuppressedExceptions);
                }
                try {
                    Field suppressedExceptionsField1 = MultiDex.findField(loader,
                            "dexElementsSuppressedExceptions");
                    IOException[] dexElementsSuppressedExceptions1 = ((IOException[]) suppressedExceptionsField1
                            .get(loader));
                    if (dexElementsSuppressedExceptions1 == null) {
                        dexElementsSuppressedExceptions1 = (IOException[]) suppressedExceptions
                                .toArray(new IOException[suppressedExceptions.size()]);
                    } else {
                        IOException[] combined = new IOException[suppressedExceptions.size()
                                + dexElementsSuppressedExceptions1.length];
                        suppressedExceptions.toArray(combined);
                        System.arraycopy(dexElementsSuppressedExceptions1, 0, combined,
                                suppressedExceptions.size(),
                                dexElementsSuppressedExceptions1.length);
                        dexElementsSuppressedExceptions1 = combined;
                    }

                    suppressedExceptionsField1.set(loader, dexElementsSuppressedExceptions1);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }

        private static Object[] makeDexElements(Object dexPathList, ArrayList<File> files,
                File optimizedDirectory, ArrayList<IOException> suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements",
                    new Class[] {
                            ArrayList.class, File.class, ArrayList.class
                    });
            return ((Object[]) makeDexElements.invoke(dexPathList, new Object[] {
                    files, optimizedDirectory, suppressedExceptions
            }));
        }

        // 适配个别机型SM-G900
        private static Object[] makeDexElementsByClassLoader(Object dexPathList,
                ArrayList<File> files, File optimizedDirectory,
                ArrayList<IOException> suppressedExceptions, ClassLoader loader)
                throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makeDexElements",
                    new Class[] {
                            ArrayList.class, File.class, ArrayList.class, ClassLoader.class
                    });
            return ((Object[]) makeDexElements.invoke(dexPathList, new Object[] {
                    files, optimizedDirectory, suppressedExceptions, loader
            }));
        }

    }

    private static final class V23 extends V19 {
        private V23() {
        }

        private static void install(ClassLoader loader, List<File> additionalClassPathEntries,
                File optimizedDirectory) throws IllegalArgumentException, IllegalAccessException,
                NoSuchFieldException, InvocationTargetException, NoSuchMethodException {
            Field pathListField = MultiDex.findField(loader, "pathList");
            Object dexPathList = pathListField.get(loader);
            ArrayList suppressedExceptions = new ArrayList();
            MultiDex.expandFieldArray(
                    dexPathList,
                    "dexElements",
                    makePathElements(dexPathList, new ArrayList(additionalClassPathEntries),
                            optimizedDirectory, suppressedExceptions));
            checkSuppressedExceptions(loader, suppressedExceptions);
        }

        private static Object[] makePathElements(Object dexPathList, ArrayList<File> files,
                File optimizedDirectory, ArrayList<IOException> suppressedExceptions)
                throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
            Method makeDexElements = MultiDex.findMethod(dexPathList, "makePathElements",
                    new Class[] {
                            List.class, File.class, List.class
                    });
            return ((Object[]) makeDexElements.invoke(dexPathList, new Object[] {
                    files, optimizedDirectory, suppressedExceptions
            }));
        }

    }

    /**
     * 反射获得对象属性，由于dex加载时要用到异常编程，很多地方需要反射失败时捕获更换方案，所以反射失败时需要将异常抛出，
     * 不要直接调用ReflectionUtils.getFieldValue
     * 
     * @param object
     * @param fieldName
     * @return
     * @throws Exception
     */
    protected static Object getFieldValue(Object object, String fieldName) throws Exception {
        // 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
        Field field = ReflectionUtils.getDeclaredField(object, fieldName);
        // 抑制Java对其的检查
        field.setAccessible(true);
        try {
            // 获取 object 中 field 所代表的属性值
            return field.get(object);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    protected static void setFieldValue(Object object, String fieldName, Object value)
            throws Exception {
        // 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
        Field field = ReflectionUtils.getDeclaredField(object, fieldName);
        // 抑制Java对其的检查
        field.setAccessible(true);
        try {
            // 将 object 中 field 所代表的值 设置为 value
            field.set(object, value);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
